/***************************************************************************
 *   Copyright (C) 2007 by Ulta (http://ultrasonic.home.pl)                *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

// Qt
#include <QtGlobal>
#include <QRgb>
#include <QImage>
#include <QVariant>
#include <QFile>
#include <QColor>

// jasper
#include <jasper/jasper.h>

// local
#include "gjpeg2khandler.h"


#define clip( x ) ( x < 0 ? 0 : ( x > 255 ? 255 : x ) )

// ------------------------ GJpeg2kHandler() ---------------------------------
GJpeg2kHandler::GJpeg2kHandler() : QImageIOHandler()
{
	// none
}


// ------------------------ ~GJpeg2kHandler() --------------------------------
GJpeg2kHandler::~GJpeg2kHandler()
{
	// none
}


// ---------------------------- canRead() ------------------------------------
bool GJpeg2kHandler::canRead() const
{
	QIODevice *dev = device();
	if ( ! dev )
	{
		return false;
	}
	// pickup 16 bytes from device
	QByteArray prefix = dev->peek(16);
	
	// chceck if it contains JPEG 2000 magic
	if ( prefix.contains("\x0d\x0a\x87\x0a") )
	{
		setFormat("jp2");
		return true;
	}
	// check jpeg codestream header
	else if ( prefix.contains("\xff\x4f\xff\x51") )
	{
		setFormat("j2c");
		return true;
	}

	return false;
}

// ---------------------------- canRead(QIODevice *) ------------------------------------
bool GJpeg2kHandler::canRead(QIODevice *dev)
{
	if ( ! dev )
	{
		return false;
	}
	// pickup 16 bytes from device
	QByteArray prefix = dev->peek(16);
	
	// chceck if it contains JPEG 2000 magic
	if ( prefix.contains("\x0d\x0a\x87\x0a") )
	{
		return true;
	}
	// check jpeg codestream header
	else if ( prefix.contains("\xff\x4f\xff\x51") )
	{
		return true;
	}

	return false;
}

// #include <sys/time.h>
// double getms()
// {
// 	struct timeval tv;
// 	gettimeofday( &tv, NULL );
// 	
// 	return (tv.tv_sec & 0xff ) * 1e3 + tv.tv_usec * 1e-3;
// }

// ----------------------------- read() --------------------------------------
bool GJpeg2kHandler::read(QImage *pOutImage )
{
// 	double msStart = getms(); // TODO debug, remove
	
	// create stream
	QIODevice* pDevice = device();
	QByteArray buffer = pDevice->readAll();
	
	jas_stream_t* pStream = jas_stream_memopen( buffer.data(), buffer.size() );
	
	if ( ! pStream )
	{
		qWarning("GJpeg2kHandler::read: can not create jasper stream");
		return false;
	}
	 
	 //qDebug("loaded: %.2lf ms", getms() - msStart ); // TODO debug, remove
	
	// decode image
	jas_image_t* pJasperImage = NULL;
	QString fmt = format();
	
	if ( fmt == "j2c" )
	{
		pJasperImage = jpc_decode( pStream, "" );
	}
	else if ( fmt == "jp2" )
	{
		pJasperImage = jp2_decode( pStream, "" );
	}
	else
	{
		qWarning("GJpeg2kHandler::read: unknown format: %s", qPrintable( fmt ) );
		jas_stream_close( pStream );
		return false;
	}
	
	//qDebug("decoded: %.2lf ms", getms() - msStart ); // TODO debug, remove
	jas_stream_close( pStream );
	
	if ( ! pJasperImage )
	{
		qWarning("GJpeg2kHandler::read: could not decode image" );
		return false;
	}
	
	int width 		= jas_image_width( pJasperImage );
	int height		= jas_image_height( pJasperImage );
	
	// copy pixels
	
	// check readed data
	int components	= jas_image_numcmpts( pJasperImage );
	int colorScheme	= jas_image_clrspc( pJasperImage );
	
	if ( jas_clrspc_fam( colorScheme ) != JAS_CLRSPC_FAM_RGB ) // TODO actualy, we can handle YUV
	{
		qWarning("GJpeg2kHandler::read: can not decode non-rgb image");
		jas_image_destroy( pJasperImage );
		return false;
	}
	
	
	// convert image
	QImage image( QSize( width, height), QImage::Format_RGB32 );
	
	
	// get component indexes
	int compIndexes[3];
	const int CR = 0, CG = 1, CB = 2;
	compIndexes[CR] = jas_image_getcmptbytype( pJasperImage, JAS_IMAGE_CT_RGB_R ); 
	compIndexes[CG] = jas_image_getcmptbytype( pJasperImage, JAS_IMAGE_CT_RGB_G ); 
	compIndexes[CB] = jas_image_getcmptbytype( pJasperImage, JAS_IMAGE_CT_RGB_B ); 
	
	// cache component x-dvisors
	int combXDiv[3];
	combXDiv[ CR ] = jas_image_cmpthstep( pJasperImage, compIndexes[CR] );
	combXDiv[ CG ] = jas_image_cmpthstep( pJasperImage, compIndexes[CG] );
	combXDiv[ CB ] = jas_image_cmpthstep( pJasperImage, compIndexes[CB] );
	
	// detect YUV
	bool yuv = false;
	if ( combXDiv[ CB ] == 2 && combXDiv[ CG ] == 2 && combXDiv[ CR ] == 1 )
	{
		yuv = true;
		// this is - in fact - yuv image with channels: Y as R, U as G and V as B
	}
	
	// create matrices for components
	jas_matrix_t* matrices[3];
	matrices[ CR ] = jas_matrix_create( height, width / combXDiv[ CR ] );
	matrices[ CG ] = jas_matrix_create( height, width / combXDiv[ CG ] );
	matrices[ CB ] = jas_matrix_create( height, width / combXDiv[ CB ] );
	
	// check matrices
	if ( ! matrices[ CR ] | ! matrices[ CR ] || ! matrices[ CR ] )
	{
		qWarning("GJpeg2kHandler::read: Can not create matrix");
		jas_image_destroy( pJasperImage );
		return false;
	}
	
	// copy data to matrices
	jas_image_readcmpt( pJasperImage, compIndexes[ CR ],
		0, 0, width / combXDiv[ CR ], height, matrices[ CR ] );
	jas_image_readcmpt( pJasperImage, compIndexes[ CG ],
		0, 0, width / combXDiv[ CG ], height, matrices[ CG ] );
	jas_image_readcmpt( pJasperImage, compIndexes[ CB ],
		0, 0, width / combXDiv[ CB ], height, matrices[ CB ] );
	
	//qDebug("copied to matrices: %.2lf ms", getms() - msStart ); // TODO debug, remove
	
	
	for( int y = 0; y < height; y++ )
	{
		quint32* pLine = (quint32*)image.scanLine( y );
		for( int x = 0; x < width; x++ )
		{
			// get components
			int C1 = jas_matrix_get( matrices[ CR ], y, x / combXDiv[CR] );
			int C2 = jas_matrix_get( matrices[ CG ], y, x / combXDiv[CG] );
			int C3 = jas_matrix_get( matrices[ CB ], y, x / combXDiv[CB] );
			
			// convert to RGB
			int R,G,B;
			if ( yuv )
			{
				int C = C1 - 16;
				int D = C2 - 128;
				int E = C3 - 128;

				R = clip(( 298 * C           + 409 * E + 128) >> 8);
				G = clip(( 298 * C - 100 * D - 208 * E + 128) >> 8);
				B = clip(( 298 * C + 516 * D           + 128) >> 8);

			}
			else
			{
				R = C1;
				G = C2;
				B = C3;
			}
			// add to QImage
			*pLine =  qRgb( R, G, B );
			pLine++;
		}// x
	}// y
	
	
	*pOutImage = image;
	
	// destroy matrices
	jas_matrix_destroy( matrices[ CR ] );
	jas_matrix_destroy( matrices[ CG ] );
	jas_matrix_destroy( matrices[ CB ] );
	
	// desteroy jasper image
	jas_image_destroy( pJasperImage );
	
// 	qDebug("jpg2k decomressed in: %.2lf ms", getms() - msStart ); // TODO debug, remove
	
	// success!
	return true;
}


// ---------------------------- write() --------------------------------------
bool GJpeg2kHandler::write(const QImage & /*image*/)
{
	return false;
}


// -------------------------- supportsOption() -------------------------------
bool GJpeg2kHandler::supportsOption(ImageOption /*option*/) const
{
	return false;
}

// ----------------------- option() ------------------------------------------
QVariant GJpeg2kHandler::option(ImageOption /*option*/) const
{
	return QVariant();
}


// ------------------------- setOption() -------------------------------------
void GJpeg2kHandler::setOption(ImageOption /*option*/, const QVariant & /*value*/)
{
	// none
}







